package cn.fanyc.action.blockchain.fbc.org2.config;

import org.apache.commons.io.IOUtils;
import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Identities;
import org.hyperledger.fabric.gateway.Identity;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.Wallets;
import org.hyperledger.fabric.gateway.X509Identity;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory;
import org.hyperledger.fabric_ca.sdk.EnrollmentRequest;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import org.hyperledger.fabric_ca.sdk.RegistrationRequest;
import org.hyperledger.fabric_ca.sdk.exception.EnrollmentException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.util.Properties;
import java.util.Set;

/**
 * 区块链相关配置
 *
 * @author Fanyc
 * @date 2020-10-18 19:19
 */
@Configuration
public class BlockChainConfiguration {


    /**
     * 初始化钱包
     *
     * @return 钱包对象
     */
    @Bean
    public Wallet wallet() throws Exception {
        // 读取证书文件
        File pemFile = ResourceUtils.getFile(GlobalConstants.PEM_FILE);
        byte[] bytes = IOUtils.toByteArray(new FileInputStream(pemFile));
        // 获取证书内容
        Properties props = new Properties();
        props.put("pemBytes", bytes);
        props.put("allowAllHostNames", "true");
        // 构建CA客户端,使用证书登录
        HFCAClient caClient = HFCAClient.createNewInstance(GlobalConstants.CA_SERVER_URI, props);
        CryptoSuite cryptoSuite = CryptoSuiteFactory.getDefault().getCryptoSuite();
        caClient.setCryptoSuite(cryptoSuite);

        // 创建一个钱包存放用户证书, 演示模拟放入内存中
        Wallet wallet = Wallets.newInMemoryWallet();//newFileSystemWallet(Paths.get("wallet"));

        // 获取Admin账号,如果钱包中没有则重新登记
        // 当前项目中是保存在内存中,所以每次都会重新登记,而实际应用会缓存在应用程序端,只需要登记一次
        if (wallet.get(GlobalConstants.USER_ADMIN) != null) {
            System.out.printf("An identity for the admin user \"%s\" already exists in the wallet%n", GlobalConstants.USER_ADMIN);
        } else {
            this.enroll(GlobalConstants.USER_ADMIN, GlobalConstants.USER_ADMIN_PWD, wallet, caClient);
        }

        // 获取普通用户账号,如果钱包中博不存在则重新登记
        if (wallet.get(GlobalConstants.USER_USER) != null) {
            System.out.printf("An identity for the user \"%s\" already exists in the wallet%n", GlobalConstants.USER_USER);
        } else {
            try {
                // 获取账号放入钱包(第一次连接区块链系统时,系统中并无次账号,需要创建账号)
                this.enroll(GlobalConstants.USER_USER, GlobalConstants.USER_USER_PWD, wallet, caClient);
            } catch (EnrollmentException e) {
                // 发生异常,则创建一个用户账号并放入钱包中
                this.registerUser(GlobalConstants.USER_USER, GlobalConstants.USER_USER_PWD, wallet, caClient);
                this.enroll(GlobalConstants.USER_USER, GlobalConstants.USER_USER_PWD, wallet, caClient);
            }
        }
        return wallet;
    }

    /**
     * 登记用户信息到钱包中
     *
     * @param username 用户名
     * @param password 密码
     * @param wallet   钱包
     * @param caClient CA客户端
     * @throws Exception 异常
     */
    private void enroll(String username, String password, Wallet wallet, HFCAClient caClient) throws Exception {
        // 构建注册请求
        final EnrollmentRequest request = new EnrollmentRequest();
        request.addHost(GlobalConstants.CA_SERVER_IP);
        request.setProfile("tls");
        // 通过CA客户端发起请求
        Enrollment enrollment = caClient.enroll(username, password, request);
        // 获取用户的身份凭据信息
        Identity user = Identities.newX509Identity(GlobalConstants.MSP_ID, enrollment);
        // 将用户身份凭据放入钱包中
        wallet.put(username, user);
        System.out.printf("Successfully enrolled user \"%s\" and imported it into the wallet%n", username);
    }

    /**
     * 创建账号
     *
     * @param username 用户名
     * @param password 密码
     * @param wallet   钱包
     * @param caClient CA客户端
     * @throws Exception 异常
     */
    private void registerUser(String username, String password, Wallet wallet, HFCAClient caClient) throws Exception {
        // 获取管理员账号,必须存在才可以创建
        X509Identity adminIdentity = (X509Identity) wallet.get(GlobalConstants.USER_ADMIN);
        if (adminIdentity == null) {
            System.out.printf("\"%s\" needs to be enrolled and added to the wallet first%n", GlobalConstants.USER_ADMIN);
        } else {
            System.out.printf("create new account \"%s\" %n", username);
            // 从钱包中获取管理员对象
            User admin = getAdminUser(wallet);

            // 注册用户信息
            RegistrationRequest registrationRequest = new RegistrationRequest(username);
            registrationRequest.setAffiliation(GlobalConstants.AFFILIATION);
            // 设置用户名、密码
            registrationRequest.setEnrollmentID(username);
            registrationRequest.setSecret(password);
            // 发起注册请求
            caClient.register(registrationRequest, admin);
        }
    }

    /**
     * 根据身份凭据构建用户对象
     *
     * @param wallet 钱包
     * @return 用户对象
     */
    private User getAdminUser(Wallet wallet) throws IOException {
        X509Identity adminIdentity = (X509Identity) wallet.get(GlobalConstants.USER_ADMIN);
        return new User() {

            @Override
            public String getName() {
                return GlobalConstants.USER_ADMIN;
            }

            @Override
            public Set<String> getRoles() {
                return null;
            }

            @Override
            public String getAccount() {
                return null;
            }

            @Override
            public String getAffiliation() {
                return GlobalConstants.AFFILIATION;
            }

            @Override
            public Enrollment getEnrollment() {
                return new Enrollment() {

                    @Override
                    public PrivateKey getKey() {
                        return adminIdentity.getPrivateKey();
                    }

                    @Override
                    public String getCert() {
                        return Identities.toPemString(adminIdentity.getCertificate());
                    }
                };
            }

            @Override
            public String getMspId() {
                return GlobalConstants.MSP_ID;
            }

        };
    }

    /**
     * 初始化应用程序访问区块链系统网关
     * @param wallet 钱包
     * @return 网关对象
     * @throws IOException IO异常
     */
    @Bean
    public Gateway gateway(Wallet wallet) throws IOException {
        // 加载CCP文件
        File networkConfigFile = ResourceUtils.getFile(GlobalConstants.CONNECTION_FILE);
        // 网关对象构造器
        Gateway.Builder builder = Gateway.createBuilder();
        // 使用普通用户身份连接到区块链系统
        builder.identity(wallet, GlobalConstants.USER_USER).networkConfig(new FileInputStream(networkConfigFile)).discovery(true);

        // 创建一个到区块链的网关连接
        return builder.connect();
    }
}
